#pragma once
#include <iostream>
#include <string>
#include <stdarg.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

// 将日志等级用整数表示
#define Info 0    // 常规
#define Debug 1   // 调试
#define Warning 2 // 警告
#define Error 3   // 错误
#define Fatal 4   // 致命

#define Screen 1
#define OneFile 2
#define ClassFile 3

class log
{
public:
    // 写一个默认构造函数, 默认打印是向屏幕打印
    log()
    {
        printMethod = Screen;
        logdir = "./logdir/"; // 你需要保证当前路径下有目录名为logdir
    }
    // 让用户选择打印方式
    void Enable(int method)
    {
        printMethod = method;
    }

    // 因为我们这里的日志等级是用一个整数表示的
    // 而最后日志打印时需要有具体是什么日志等级
    // 因此我们可以封装一个函数将日志等级转化为字符串
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    // level - 日志等级
    // format - 格式化字符串的数据格式。类似于printf前半部分
    // ... - 表示可变参数
    void logmessage(int level, const char *format, ...)
    {
        // ====== 默认部分：日志等级 + 时间 =========
        time_t _timestamp = time(NULL); // time函数会返回时间戳
        // 再将time_t类型转化为struct tm类型
        struct tm *_tm = localtime(&_timestamp);

        char defaultPart[1024]; // 默认部分
                                // 打印的日志格式：[日志等级][时间]
        snprintf(defaultPart, sizeof(defaultPart), "[%s][%d-%d-%d:%d:%d:%d]",
                 levelToString(level).c_str(), _tm->tm_year + 1900, _tm->tm_mon + 1, _tm->tm_mday,
                 _tm->tm_hour, _tm->tm_min, _tm->tm_sec);

        // ====== 自定义部分：format内容 + 可变参数... =========
        char self[1024];
        va_list s;
        va_start(s, format);
        vsnprintf(self, sizeof(self), format, s);
        va_end(s);

        // ===== 将默认部分和自定义部分整合 =====
        char logtxt[2048];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", defaultPart, self);

        // ==== 信息全部在logtxt中，你可以打印出来，或者写到一个文件里 ======
        // printf("%s", logtxt); // 直接打印

        printLog(level, logtxt);
    }

    // 封装打印日志文件的方法：1. 向屏幕打印 2. 向文件打印 3. 分类打印
    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case OneFile:
            printOneFile("log.txt", logtxt);
            break;
        case ClassFile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }

    // 向一个文件写
    void printOneFile(const std::string filename, const std::string &logtxt)
    {
        std::string _filename = logdir + filename; // ./logdir/log.txt

        int fd = open(_filename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
        if (fd < 0)
        {
            return;
        }
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }

    // 文件分类写。比如Info信息放在一个文件中，Errno放在一个文件中...
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = "log.txt";
        filename += '.';
        filename += levelToString(level);

        printOneFile(filename, logtxt);
    }

private:
    int printMethod;
    std::string logdir; // 日志文件存放目录
};